package br.com.tarcilahelpdesk.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Restrictions;
import org.primefaces.model.LazyDataModel;

/**
 * Classe para generalizar o acesso a dados usando Hibernate. As consultas serão feitas pelo objeto parâmetro passado no construtor da classe.
 * O objeto passado como parâmetro deve estender a classe ObjetoPersistente, para receber um Id e um campo de versionamento.
 * @author vinicius.fonseca
 * @param <ObjetoPersistente> 
 */
public class DAOGenerico<ObjetoPersistente> implements Serializable
{

    private SessionFactory factory = HibernateUtil.getSessionFactory();
    private Class classeDAO;

    /**
     * Construtor. Seta a classe deste DAO.
     * @param classe 
     */
    public DAOGenerico(Class classe)
    {
        this.classeDAO = classe;
    }

    /**
     * Retorna a classe passada como parâmetro.
     * @return Class
     */
    public Class getClasseDAO()
    {
        return classeDAO;
    }

    /**
     * Salva a o objeto de parâmetro.
     * @param obj = Objeto a ser salvo.
     */
    public void salvar(Object obj)
    {
        Session ss = factory.openSession();
        Transaction tx = ss.beginTransaction();

        try
        {
            ss.saveOrUpdate(obj);
            tx.commit();
        }
        catch (RuntimeException e)
        {
            tx.rollback();
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Salva a o objeto de parâmetro.
     * @param obj = objeto a ser salvo.
     * @param ss = Sessao do hibernate.
     */
    public void salvar(Object obj, Session ss)
    {

        Transaction tx = ss.beginTransaction();

        try
        {
            ss.saveOrUpdate(obj);
            //ss.merge(obj);
            tx.commit();
        }
        catch (RuntimeException e)
        {
            tx.rollback();
            throw e;
        }
    }
    
    /**
     * Remove o objeto passado como parametro.
     * @param obj = Objeto a ser removido.
     */
    public void remover(Object obj)
    {
        Session ss = factory.openSession();
        Transaction tx = ss.getTransaction();

        try
        {
            tx.begin();
            ss.delete(obj);
            tx.commit();
        }
        catch (RuntimeException e)
        {
            tx.rollback();
            throw e;
        }
        finally
        {
            ss.close();
        }

    }

    /**
     * Remove o objeto passado como parametro.
     * @param obj = Objeto a ser removido.
     * @param ss = Sessao do hibernate. 
     */
    public void remover(Object obj, Session ss)
    {
        Transaction tx = ss.getTransaction();

        try
        {
            tx.begin();
            //Necessário para "repersistir" objeto em estado desatachado.
            //obj = ss.merge(obj);
            ss.delete(obj);
            tx.commit();
        }
        catch (RuntimeException e)
        {
            tx.rollback();
            throw e;
        }

    }

    /**
     * Busca o objeto pelo ID.
     * @param id = ID do objeto.
     * @return Objeto com id informado.
     */
    public Object buscarPorID(Long id)
    {

        Session ss = factory.openSession();

        try
        {
            Object obj;
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.add(Restrictions.eq("id", id));
            obj = crit.uniqueResult();
            return obj;
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }

    }

    /**
     * Busca i objeto pelo ID.
     * @param id = ID do objeto.
     * @param ss = Sessão do hibernate.
     * @return Object = Objeto com id informado.
     */
    public Object buscarPorID(Long id, Session ss)
    {
        try
        {
            Object obj;
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.add(Restrictions.eq("id", id));
            obj = crit.uniqueResult();
            return obj;
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Busca todos os objetos do tipo informado na instancia da classe.
     * @return List<Object> = Lista de objetos.
     */
    public List<Object> buscarTudo()
    {

        Session ss = factory.openSession();

        try
        {
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Busca todos os objetos do tipo informado na instancia da classe.
     * @param ss
     * @return List<Object> = Lista de objetos.
     */
    public List<Object> buscarTudo(Session ss)
    {
        try
        {
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Busca todos os objetos da classe com base em uma lista de Criterions e uma lista de Projections informada como parâmetro.
     * @param listaRestrictions = Lista de Criterions (Hibernate) desta busca. 
     * @param listaProjections = Lista de Projections (Hibernate) desta busca.
     * @return List<Object> = Lista de Objetos
     */
    public List<Object> buscarRestrictionsProjections(List<Criterion> listaRestrictions, ProjectionList listaProjections)
    {

        Session ss = factory.openSession();

        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());

            if (listaRestrictions != null && !listaRestrictions.isEmpty())
            {
                for (int i = 0; i < listaRestrictions.size(); i++)
                {
                    crit.add(listaRestrictions.get(i));
                }
            }
            if (listaProjections != null)
            {
                crit.setProjection(listaProjections);
            }

            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Busca todos os objetos da classe com base em uma lista de Criterions e uma lista de Projections informada como parâmetro.
     * @param listaRestrictions = Lista de Criterions (Hibernate) desta busca. 
     * @param listaProjections = Lista de Projections (Hibernate) desta busca.
     * @param ss = Sessão do hibernate.
     * @return List<Object> = Lista de Objetos.
     */
    public List<Object> buscarRestrictionsProjections(List<Criterion> listaRestrictions, ProjectionList listaProjections, Session ss)
    {

        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());

            if (listaRestrictions != null && !listaRestrictions.isEmpty())
            {
                for (int i = 0; i < listaRestrictions.size(); i++)
                {
                    crit.add(listaRestrictions.get(i));
                }
            }
            if (listaProjections != null)
            {
                crit.setProjection(listaProjections);
            }

            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Busca todos os objetos da classeDAO ordenados.
     * Informar true para ordenacao ascendente e false para ordenação descendente.
     * @param campoParaOrdenar = Campo para ser ordenado pela busca.
     * @param asc = True para ordenacao ascendente, false para ordenacao descendente.
     * @return List<Object> Lista de Objetos.
     */
    public List<Object> buscarTudoOrdenado(String campoParaOrdenar, boolean asc)
    {

        Session ss = factory.openSession();

        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            if (asc == true)
            {
                crit.addOrder(Order.asc(campoParaOrdenar));
            }
            else
            {
                crit.addOrder(Order.desc(campoParaOrdenar));
            }
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Busca todos os objetos da classeDAO ordenados.
     * Informar true para ordenação ascendente e false para ordenação descendente.
     * @param camposParaOrdenar = Lista de campos para ordenacao.
     * @param asc = True para ordenacao ascendente, false para ordenacao descendente.
     * @return List<Object> Lista de Objetos.
     */
    public List<Object> buscarTudoOrdenado(List<String> camposParaOrdenar, boolean asc)
    {

        Session ss = factory.openSession();

        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            if (camposParaOrdenar != null && !camposParaOrdenar.isEmpty())
            {
                if (asc == true)
                {
                    for (Iterator<String> it = camposParaOrdenar.iterator(); it.hasNext();)
                    {
                        String campo = it.next();
                        crit.addOrder(Order.asc(campo));
                    }
                }
                else
                {
                    for (Iterator<String> it = camposParaOrdenar.iterator(); it.hasNext();)
                    {
                        String campo = it.next();
                        crit.addOrder(Order.desc(campo));
                    }
                }
            }
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Busca todos os Objetos da Classe informada ordenados.
     * Informar true para ordenação ascendente e false para ordenação descendente.
     * @param campoParaOrdenar = Campo para ordenação.
     * @param asc = True para ordenacao ascendente, false para ordenacao descendente.
     * @param ss = Sessao do hibernate.
     * @return List<Object> = Lista de Objetos ordenados.
     */
    public List<Object> buscarTudoOrdenado(String campoParaOrdenar, boolean asc, Session ss)
    {
        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            if (asc == true)
            {
                crit.addOrder(Order.asc(campoParaOrdenar));
            }
            else
            {
                crit.addOrder(Order.desc(campoParaOrdenar));
            }
            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Este método gera um modelo LazyDataModel de 10 paginas, com função de busca paginada, filtro e ordenação, utilizando hibernate. 
     * Este objeto deve ser referenciado ao campo "value" de um DataTable do PrimeFaces. 
     * @param ss = Sessao do hibernate.
     * @return Retorna um objeto LazyDataModel<Tipo>.
     */
    public LazyDataModel<ObjetoPersistente> getModeloPrimeFaces(Session ss)
    {
        try
        {
            PFDataModel modelo = new PFDataModel(getClasseDAO(), ss, this);            
            return (LazyDataModel<ObjetoPersistente>) modelo;
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Este método gera um modelo LazyDataModel de 10 paginas, com função de busca paginada, filtro e ordenação, utilizando hibernate. 
     * Este objeto deve ser referenciado ao campo "value" de um DataTable do PrimeFaces. 
     * @param listaRestrictions = Lista de restrictions para geração do LazyDataModel. Informar null se não houver Restrictions a serem adicionadas.
     * @param listaProjections 
     * @param ss = Sessao do hibernate.
     * @return Retorna um objeto LazyDataModel<Tipo>.
     */
    public LazyDataModel<ObjetoPersistente> getModeloPrimeFaces(List<Criterion> listaRestrictions, ProjectionList listaProjections, Session ss)
    {

        try
        {

            PFDataModel modelo = new PFDataModel(getClasseDAO(), this, listaRestrictions, listaProjections, ss);
            modelo.setPageSize(10);
           // modelo.setRowCount(crit.list().size());

            return (LazyDataModel<ObjetoPersistente>) modelo;

        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Busca o objeto passado como parâmetro.
     * @param obj = Objeto a ser buscado.
     * @return Objeto passado como parâmetro.
     */
    public Object buscarExemplo(Object obj)
    {
        Session ss = factory.openSession();

        try
        {
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.add(Example.create(obj).excludeZeroes());
            return crit.uniqueResult();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Busca o objeto passado como parâmetro.
     * @param obj = Objeto a ser buscado.
     * @param ss = Sessao do hibernate.
     * @return Objeto passado como parâmetro.
     */
    public Object buscarExemplo(Object obj, Session ss)
    {
        try
        {
            Criteria crit = ss.createCriteria(getClasseDAO());
            crit.add(Example.create(obj).excludeZeroes());
            return crit.uniqueResult();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Cria uma lista paginada do objeto informado na instancia da classe.
     * @param paginaInicial
     * @param qtdePorPagina
     * @return Retorna uma lista do objeto de acordo com os parametros.
     */
    public List<Object> buscarPaginado(int paginaInicial, int qtdePorPagina)
    {
        Session ss = factory.openSession();

        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());

            //Paginacão
            crit.setFirstResult(paginaInicial);
            crit.setMaxResults(qtdePorPagina);

            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Cria uma lista paginada dos objetos informados na ClasseDAO
     * @param paginaInicial
     * @param qtdePorPagina
     * @param ss
     * @return Lista de Objetos paginada.
     */
    public List<Object> buscarPaginado(int paginaInicial, int qtdePorPagina, Session ss)
    {
        try
        {

            Criteria crit = ss.createCriteria(getClasseDAO());

            //Paginacão
            crit.setFirstResult(paginaInicial);
            crit.setMaxResults(qtdePorPagina);

            return crit.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Executa uma consulta em SQL.
     * @param sql = String em SQL.
     * @return Lista de Objetos da consulta informaca como parâmetro.
     */
    public List<Object> consultaSQL(String sql)
    {

        Session ss = factory.openSession();

        try
        {
            Query query = ss.createSQLQuery(sql);
            return query.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Executa uma consulta em SQL.
     * @param sql = String em SQL.
     * @param ss = Sessao do hibernate.
     * @return Lista de Objetos
     */
    public List<Object> consultaSQL(String sql, Session ss)
    {

        try
        {
            Query query = ss.createSQLQuery(sql);
            return query.list();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Executa uma consulta em HQL com parâmetros.
     * @param hql = String HQL.
     * @param parametros = Lista de objetos parametros da string HQL. Informar null se nao possuir parametros.
     * @return Lista de Objetos encontrados que atendam aos parâmetros informados.
     */
    public List<Object> consultaHQL(String hql, List<Object> parametros)
    {

        Session ss = factory.openSession();

        try
        {

            if (hql != null && !hql.isEmpty())
            {

                Query query = ss.createQuery(hql);

                int count = 0;

                if (parametros != null && !parametros.isEmpty())
                {
                    for (Object object : parametros)
                    {
                        query.setParameter(count, object);
                        count++;
                    }
                }

                return query.list();
            }
            return new ArrayList<Object>();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Realiza uma consulta em HQL com parametros.
     * @param hql = String HQL.
     * @param parametros = Lista de objetos parametros da string HQL. Informar null se nao possuir parametros.
     * @param ss
     * @return Lista de Objetos
     */
    public List<Object> consultaHQL(String hql, List<Object> parametros, Session ss)
    {

        try
        {

            if (hql != null && !hql.isEmpty())
            {

                Query query = ss.createQuery(hql);

                int count = 0;

                if (parametros != null && !parametros.isEmpty())
                {
                    for (Object object : parametros)
                    {
                        query.setParameter(count, object);
                        count++;
                    }
                }

                return query.list();
            }
            return new ArrayList<Object>();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    /**
     * Executa uma consulta HQL que retorna um objeto único.
     * @param hql = String em HQL
     * @return Object = Objeto único retornado da busca.
     */
    public Object consultaUnicaHQL(String hql)
    {

        Session ss = factory.openSession();

        try
        {
            Query query = ss.createQuery(hql);
            return query.uniqueResult();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        finally
        {
            ss.close();
        }
    }

    /**
     * Realiza uma consulta em HQL
     * @param hql = String HQL.
     * @param ss = Sessao do hibernate.
     * @return Object = Objeto único da busca.
     */
    public Object consultaUnicaHQL(String hql, Session ss)
    {
        try
        {
            Query query = ss.createQuery(hql);
            return query.uniqueResult();
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }
}
